-- 
-- Please see the readme.txt file included with this distribution for 
-- attribution and copyright information.
--

function onInit()
	-- REGISTER A GENERAL HANDLER
	ChatManager.registerRollHandler("", processDiceLanded);
	
	-- REGISTER SPECIFIC HANDLERS
	ChatManager.registerRollHandler("save", processSaveRoll);
	ChatManager.registerRollHandler("autosave", processSaveRoll);
	ChatManager.registerRollHandler("recharge", processRechargeRoll);
end

--function onClose()
	-- UNREGISTER A GENERAL HANDLER
	--ChatManager.unregisterRollHandler("", processDiceLanded);
	
	-- UNREGISTER SPECIFIC HANDLERS
	--ChatManager.unregisterRollHandler("save", processSaveRoll);
	--ChatManager.unregisterRollHandler("autosave", processSaveRoll);
	--ChatManager.unregisterRollHandler("recharge", processRechargeRoll);
--end

function checkDie(roll, sides, index, max_array, brutal_array, isBrutal, isVorpal, bonus, reroll_str, add_str)
	-- HANDLE MAX
	if max_array[index] then
		return sides, bonus, reroll_str, add_str;
	end
	
	-- HANDLE VORPAL
	if isVorpal then
		-- IF MAX ROLL, THEN ROLL AGAIN
		if roll == sides then
			-- ROLL NEW DIE
			local new_die = math.random(sides);

			-- CHECK THE NEW DIE
			new_die, bonus, reroll_str, add_str = checkDie(new_die, sides, index, max_array, brutal_array, isBrutal, isVorpal, bonus, reroll_str, add_str);

			-- ADD ANY CHANGES
			add_str = " " .. new_die .. add_str;
			bonus = bonus + new_die;
		end
	end
	
	-- HANDLE BRUTAL
	if brutal_array[index] and isBrutal > 0 and roll <= isBrutal then
		
		-- IF BRUTAL IS WITHIN ONE OF NUMBER OF SIDES, THEN JUST RETURN MAX
		if isBrutal >= sides - 1 then
			return sides, bonus, reroll_str, add_str;
		
		-- OR ELSE REROLL DICE WHICH DID NOT MEET THE BRUTAL CUTOFF
		else
			-- ADD FAILED ROLL TO OUTPUT
			reroll_str = reroll_str .. " " .. roll;

			-- ROLL NEW DIE
			local new_die = math.random(sides);

			-- CHECK DIE AGAIN
			roll, bonus, reroll_str, add_str = checkDie(new_die, sides, index, max_array, brutal_array, isBrutal, isVorpal, bonus, reroll_str, add_str);
		end
	end
	
	-- Return the results of the die check
	return roll, bonus, reroll_str, add_str;
end

function createRollMessage(draginfo, rSourceActor)
	-- UNPACK DATA
	local sType = draginfo.getType();
	local sDesc = draginfo.getDescription();

	-- DETERMINE META-ROLL MODIFIERS
	-- NOTE: SHOULD BE MUTUALLY EXCLUSIVE
	local isDiceTower = string.match(sDesc, "^%[TOWER%]");
	local isGMOnly = string.match(sDesc, "^%[GM%]");
	
	-- APPLY MOD STACK, IF APPROPRIATE
	if not isDiceTower and not (sType == "autosave") and not (sType == "recharge") then
		ModifierStack.applyToRoll(draginfo);
		sDesc = draginfo.getDescription();
	end

	-- DICE TOWER AND GM FLAG HANDLING
	if isDiceTower then
		-- MAKE SURE SOURCE ACTOR IS EMPTY
		rSourceActor = nil;
	elseif isGMOnly then
		-- STRIP GM FROM DESCRIPTION
		sDesc = string.sub(sDesc, 6);
	end

	-- Build the basic message to deliver
	local rMessage = ChatManager.createBaseMessage(rSourceActor);
	rMessage.text = rMessage.text .. sDesc;
	rMessage.dice = draginfo.getDieList() or {};

	-- GET THE DIE MODIFIER
	-- NOTE: CAPTURING CTRL KEY NUMBER NEGATION AND REVERSING, SINCE IT SHOULD NOT APPLY ON DICE ROLLS
	if Input.isControlPressed() then
		rMessage.diemodifier = 0 - draginfo.getNumberData();
	else
		rMessage.diemodifier = draginfo.getNumberData();
	end
	
	-- Check to see if this roll should be secret (GM or dice tower tag)
	if isDiceTower then
		rMessage.dicesecret = true;
		rMessage.sender = "";
		rMessage.icon = "dicetower_icon";
	elseif isGMOnly then
		rMessage.dicesecret = true;
		rMessage.text = "[GM] " .. rMessage.text;
	elseif User.isHost() and OptionsManager.isOption("REVL", "off") then
		rMessage.dicesecret = true;
	end

	-- RETURN MESSAGE RECORD
	return rMessage;
end

function processDiceLanded(draginfo)
	-- Figure out what type of roll we're handling
	local dragtype = draginfo.getType();
	local dragdesc = draginfo.getDescription();
	
	-- Get actors
	local rSourceActor, rTargetActor = CombatCommon.getActionActors(draginfo);
	local bMultiRoll = false;
	if dragtype == "attack" or dragtype == "damage" then
		if rTargetActor and rTargetActor.sType == "multi" then
			local sTargetIndex = string.match(dragdesc, "%[TARGET (%d+)%]");
			if sTargetIndex or dragtype == "attack" then
				local nTargetIndex = tonumber(sTargetIndex) or 1;
				rTargetActor = rTargetActor.aActors[nTargetIndex];
				bMultiRoll = true;
			end
		end
	end

	-- BUILD THE BASIC ROLL MESSAGE
	local entry = createRollMessage(draginfo, rSourceActor);

	-- BUILD TOTALS AND HANDLE SPECIAL CONDITIONS FOR DAMAGE ROLLS
	local reroll_string = "";
	local add_string = "";
	local total = 0;
	if dragtype == "damage" and string.match(dragdesc, "%[DAMAGE") then
		-- IS MAX?
		local max_array = {};
		if string.match(dragdesc, "%[MAX") then
			local sMaxDiceString = string.match(dragdesc, "%[MAX %(D([%d,%-]+)%)%]");
			if sMaxDiceString then
				local max_list = StringManager.split(sMaxDiceString, ",");
				for k,v in pairs(max_list) do
					local max_startstr, max_endstr = string.match(v, "(%d+)%-?(%d*)");
					local max_start = tonumber(max_startstr) or 0;
					local max_end = tonumber(max_endstr) or 0;
					if max_start > 0 then
						if max_end > 0 then
							for i = max_start, max_end do
								max_array[i] = true;
							end
						else
							max_array[max_start] = true;
						end
					end
				end
			else
				for i = 1, #(entry.dice) do
					max_array[i] = true;
				end
			end
		end
		
		-- IS VORPAL?
		local isVorpal = false;
		if string.match(dragdesc, " %[VORPAL%]") then
			isVorpal = true;
		end
		
		-- IS BRUTAL?
		local isBrutal = 0;
		local brutal_array = {};
		if string.match(dragdesc, "%[BRUTAL") then
			local sBrutalLimitString = string.match(dragdesc, "%[BRUTAL (%d)");
			isBrutal = tonumber(sBrutalLimitString) or 0;
			
			if isBrutal > 0 then
				local sBrutalDiceString = string.match(dragdesc, "%[BRUTAL %d %(D([%d,%-]+)%)%]");
				if sBrutalDiceString then
					local brutal_list = StringManager.split(sBrutalDiceString, ",");
					for k,v in pairs(brutal_list) do
						local brutal_startstr, brutal_endstr = string.match(v, "(%d+)%-?(%d*)");
						local brutal_start = tonumber(brutal_startstr) or 0;
						local brutal_end = tonumber(brutal_endstr) or 0;
						if brutal_start > 0 then
							if brutal_end > 0 then
								for i = brutal_start, brutal_end do
									brutal_array[i] = true;
								end
							else
								brutal_array[brutal_start] = true;
							end
						end
					end
				else
					for i = 1, #(entry.dice) do
						brutal_array[i] = true;
					end
				end
			end
		end
		
		-- DETERMINE DAMAGE TYPES FOR THIS ROLL
		local dmgtype_roll = {};
		for dmgtypestr, dmg_diecount, dmg_sign, dmg_mod in string.gmatch(dragdesc, "%[TYPE: ([^()]+) %((%d+)D([%+%-]?)(%d*)%)%]") do
			dmg_diecount = tonumber(dmg_diecount) or 0;
			dmg_mod = tonumber(dmg_mod) or 0;
			if dmg_diecount > 0 or dmg_mod > 0 then
				table.insert(dmgtype_roll, {dmgtype = dmgtypestr, diecount = dmg_diecount, sign = dmg_sign, mod = dmg_mod});
			end
		end
		
		-- BUILD THE TOTALS
		local dmgtype_index = 1;
		local dmgtype_count = 0;
		local dmgtype_total = 0;
		for i,d in ipairs(entry.dice) do
			
			-- PROCESS EACH DIE FOR SPECIAL CONDITIONS
			local die_bonus = 0;
			local die_sides = tonumber(string.match(d.type, "d(%d+)")) or 0;
			if die_sides > 0 then
				local die_reroll_str = "";
				local die_add_str = "";

				d.result, die_bonus, die_reroll_str, die_add_str = checkDie(d.result, die_sides, i, max_array, brutal_array, isBrutal, isVorpal, die_bonus, die_reroll_str, die_add_str);
				entry.diemodifier = entry.diemodifier + die_bonus;

				if die_reroll_str ~= "" then
					if reroll_string ~= "" then
						reroll_string = reroll_string .. ",";
					end
					reroll_string = reroll_string .. " D" .. i .. ":" .. die_reroll_str;
				end
				if die_add_str ~= "" then
					if add_string ~= "" then
						add_string = add_string .. ",";
					end
					add_string = add_string .. " D" .. i .. ":" .. die_add_str;
				end
			end

			-- DETERMINE OVERALL TOTAL
			total = total + d.result;
			
			-- DETERMINE THE DAMAGE TYPE SUB-TOTALS
			if dmgtype_index <= #dmgtype_roll then
				dmgtype_count = dmgtype_count + 1;
				dmgtype_total = dmgtype_total + d.result + die_bonus;
				
				if dmgtype_count >= dmgtype_roll[dmgtype_index].diecount then
					if dmgtype_roll[dmgtype_index].sign == "-" then
						dmgtype_total = dmgtype_total - dmgtype_roll[dmgtype_index].mod;
					else
						dmgtype_total = dmgtype_total + dmgtype_roll[dmgtype_index].mod;
					end
					dmgtype_roll[dmgtype_index].total = dmgtype_total;
					
					dmgtype_index = dmgtype_index + 1;
					dmgtype_count = 0;
					dmgtype_total = 0;
				end
			end
		end
		
		-- HANDLE ANY REMAINING FIXED DAMAGE
		for i = dmgtype_index, #dmgtype_roll do
			dmgtype_roll[i].total = dmgtype_roll[i].mod;
		end
		
		-- ADD DAMAGE TYPE SUB-TOTALS
		for i = 1, #dmgtype_roll do
			local sFind = "(%[TYPE: " .. dmgtype_roll[i].dmgtype .. " %(" .. dmgtype_roll[i].diecount .. "D";
			if dmgtype_roll[i].mod ~= 0 then
				sFind = sFind .. "%" .. dmgtype_roll[i].sign .. dmgtype_roll[i].mod;
			end
			sFind = sFind .. ")(%)%])";
			local sRepl = "%1=" .. dmgtype_roll[i].total .. "%2";
			entry.text = string.gsub(entry.text, sFind, sRepl, 1);
		end
		
	-- BUILD BASIC TOTAL FOR NON-DAMAGE ROLLS
	else
		for i,d in ipairs(entry.dice) do
			total = total + d.result;
		end
	end
	
	-- Add the roll modifier to the total
	total = total + entry.diemodifier;

	-- Report any brutal rerolls or vorpal adding rolls
	local result_str = "";
	if reroll_string ~= "" then
		result_str = result_str .. " [REROLL " .. reroll_string .. "]";
	end
	if add_string ~= "" then
		result_str = result_str .. " [ADD " .. add_string .. "]";
	end
	
	-- Check for special attack results
	local attack_result_str = "";
	local add_total = true;
	local sAttackResult = "";
	if dragtype == "attack" then
		-- If we have a target, then calculate the defense we need to exceed
		local defenseval = nil;
		if User.isHost() or OptionsManager.isOption("PATK", "on") then
			local nDefEffectsBonus = 0;
			local nAtkEffectsBonus = 0;
			defenseval, nAtkEffectsBonus, nDefEffectsBonus = RulesManager.getDefenseValue(rSourceActor, rTargetActor, dragdesc);
			if nAtkEffectsBonus ~= 0 then
				attack_result_str = attack_result_str .. string.format(" [EFFECTS %+d]", nAtkEffectsBonus);
			end
			if nDefEffectsBonus ~= 0 then
				attack_result_str = attack_result_str .. string.format(" [DEF EFFECTS %+d]", nDefEffectsBonus);
			end
		end

		-- Get the crit threshold
		local crit_threshold = 20;
		local sAltCritRange = string.match(dragdesc, "%[CRIT (%d+)%]");
		if sAltCritRange then
			crit_threshold = tonumber(sAltCritRange) or 20;
			if (crit_threshold <= 1) or (crit_threshold > 20) then
				crit_threshold = 20;
			end
		end
		
		-- Check for the result of the attack roll based on the die roll and the defense
		local isSpecialHit = false;
		local first_die = entry.dice[1].result or 0;
		if first_die >= 20 then
			isSpecialHit = true;
			if not (rSourceActor and rSourceActor.sType == "pc") and OptionsManager.isOption("REVL", "off") then
				ChatManager.Message("[GM] Original attack = " .. first_die .. "+" .. entry.diemodifier .. "=" .. total, false);
				entry.diemodifier = 0;
				add_total = false;
			end
			if defenseval then
				if total >= defenseval then
					attack_result_str = attack_result_str .. " [CRITICAL HIT]";
					sAttackResult = "crit";
				else
					attack_result_str = attack_result_str .. " [AUTOMATIC HIT]";
					sAttackResult = "hit";
				end
			else
				attack_result_str = attack_result_str .. " [AUTOMATIC HIT, CHECK FOR CRITICAL]";
			end
		elseif first_die == 1 then
			isSpecialHit = true;
			if not (rSourceActor and rSourceActor.sType == "pc") and OptionsManager.isOption("REVL", "off") then
				entry.diemodifier = 0;
				add_total = false;
			end
			attack_result_str = attack_result_str .. " [AUTOMATIC MISS]";
			if defenseval then
				sAttackResult = "miss";
			end
		elseif defenseval then
			if total >= defenseval then
				if first_die >= crit_threshold then
					attack_result_str = attack_result_str .. " [CRITICAL HIT]";
					sAttackResult = "crit";
				else
					attack_result_str = attack_result_str .. " [HIT]";
					sAttackResult = "hit";
				end
			else
				attack_result_str = attack_result_str .. " [MISS]";
				sAttackResult = "miss";
			end
		elseif first_die >= crit_threshold then
			attack_result_str = attack_result_str .. " [CHECK FOR CRITICAL]";
		end
	end
			
	-- Add the total, if the auto-total option is on
	if OptionsManager.isOption("TOTL", "on") and add_total then
		result_str = result_str .. " [" .. total .. "]";
	end
	
	-- Add any special results
	if result_str ~= "" then
		entry.text = entry.text .. result_str;
	end

	-- Deliver the chat entry
	ChatManager.deliverMessage(entry);
	
	-- Special handling of final results
	if dragtype == "attack" then
		if rTargetActor then
			ChatManager.messageAttack(entry.dicesecret, total, rTargetActor, attack_result_str);
			if sAttackResult == "miss" and (OptionsManager.isOption("RMMT", "on") or (OptionsManager.isOption("RMMT", "multi") and bMultiRoll)) then
				local sTargetType = "client";
				if User.isHost() then
					sTargetType = "host";
				end
				TargetingManager.removeTarget(sTargetType, rSourceActor.nodeCT, rTargetActor.sCTNode);
			end
		end
	
	elseif dragtype == "damage" then
		-- Apply damage to the PC or combattracker entry referenced
		if rTargetActor then
			if rTargetActor.sType == "pc" and rTargetActor.nodeCreature then
				if User.isHost() or OptionsManager.isOption("PDMG", "on") then
					if rSourceActor then
						ChatManager.sendSpecialMessage(ChatManager.SPECIAL_MSGTYPE_APPLYDMG, {total, entry.text, rTargetActor.sType, rTargetActor.sCreatureNode, rTargetActor.sCTNode, rSourceActor.sCTNode});
					else
						ChatManager.sendSpecialMessage(ChatManager.SPECIAL_MSGTYPE_APPLYDMG, {total, entry.text, rTargetActor.sType, rTargetActor.sCreatureNode, rTargetActor.sCTNode, ""});
					end
				else
					local rDamageOutput = RulesManager.decodeDamageText(total, entry.text);
					ChatManager.messageDamage(entry.dicesecret, rDamageOutput.sTypeOutput, rDamageOutput.sVal, rTargetActor);
				end
			elseif rTargetActor.nodeCT then
				if User.isHost() or OptionsManager.isOption("PDMG", "on") then
					if rSourceActor then
						ChatManager.sendSpecialMessage(ChatManager.SPECIAL_MSGTYPE_APPLYDMG, {total, entry.text, rTargetActor.sType, rTargetActor.sCreatureNode, rTargetActor.sCTNode, rSourceActor.sCTNode});
					else
						ChatManager.sendSpecialMessage(ChatManager.SPECIAL_MSGTYPE_APPLYDMG, {total, entry.text, rTargetActor.sType, rTargetActor.sCreatureNode, rTargetActor.sCTNode, ""});
					end
				else
					local rDamageOutput = RulesManager.decodeDamageText(total, entry.text);
					ChatManager.messageDamage(entry.dicesecret, rDamageOutput.sTypeOutput, rDamageOutput.sVal, rTargetActor);
				end
			elseif rTargetActor.sType == "multi" then
				for keyTarget, rTempActor in pairs(rTargetActor.aActors) do
					if User.isHost() or OptionsManager.isOption("PDMG", "on") then
						if rSourceActor then
							ChatManager.sendSpecialMessage(ChatManager.SPECIAL_MSGTYPE_APPLYDMG, {total, entry.text, rTempActor.sType, rTempActor.sCreatureNode, rTempActor.sCTNode, rSourceActor.sCTNode});
						else
							ChatManager.sendSpecialMessage(ChatManager.SPECIAL_MSGTYPE_APPLYDMG, {total, entry.text, rTempActor.sType, rTempActor.sCreatureNode, rTempActor.sCTNode, ""});
						end
					else
						local rDamageOutput = RulesManager.decodeDamageText(total, entry.text);
						ChatManager.messageDamage(entry.dicesecret, rDamageOutput.sTypeOutput, rDamageOutput.sVal, rTempActor);
					end
				end
			end
		end

	elseif dragtype == "init" then
		-- Set the initiative for this creature in the combat tracker
		if rSourceActor then
			NodeManager.set(rSourceActor.nodeCT, "initresult", "number", total);
		end
	end
end

function processSaveRoll(draginfo)
	-- GET ACTORS
	local rSourceActor, rTargetActor, nodeTargetEffect = CombatCommon.getActionActors(draginfo);
	
	-- BUILD THE BASIC ROLL MESSAGE
	local entry = createRollMessage(draginfo, rSourceActor);

	-- CALCULATE TOTAL
	local total = 0;
	for i,d in ipairs(entry.dice) do
		total = total + d.result;
	end
	total = total + entry.diemodifier;
	
	-- DELIVER ROLL MESSAGE
	ChatManager.deliverMessage(entry);
	
	-- DELIVER RESULTS MESSAGE
	CombatCommon.onSave(draginfo.getDescription(), total, rSourceActor, nodeTargetEffect);
end

function processRechargeRoll(draginfo)
	-- VALIDATE
	if not User.isHost() then
		ChatManager.SystemMessage("[ERROR] Received recharge roll on client");
		return;
	end
	
	-- GET ACTORS
	local rSourceActor, rTargetActor, nodeTargetEffect = CombatCommon.getActionActors(draginfo);
	
	-- BUILD THE BASIC ROLL MESSAGE
	local entry = createRollMessage(draginfo, rSourceActor);

	-- CALCULATE TOTAL
	local total = 0;
	for i,d in ipairs(entry.dice) do
		total = total + d.result;
	end
	total = total + entry.diemodifier;

	-- Determine whether we have the right references to automatically handle recharge
	if nodeTargetEffect then
		-- Check the effect components
		local sEffectName = NodeManager.get(nodeTargetEffect, "label", "");
		local effectlist = EffectsManager.parseEffect(sEffectName);
		local recharge_val = nil;
		local recharge_name = "";
		for i = 1, #effectlist do
			if effectlist[i].type == "RCHG" then
				recharge_val = effectlist[i].mod;
				recharge_name = table.concat(effectlist[i].remainder, " ");
				break;
			end
		end
		
		-- Make sure that the die roll exceeds the recharge value
		if recharge_val and total >= recharge_val then
			-- If we successfully recharged, note it and remove the effect
			entry.text = entry.text .. " [RECHARGED]";
			local effectlistnode = nodeTargetEffect.getParent();
			nodeTargetEffect.delete();
			if effectlistnode.getChildCount() == 0 then
				NodeManager.createChild(effectlistnode);
			end

			-- Also, remove the [USED] marker from Atk line
			local nodeAttacks = effectlistnode.getChild("..attacks");
			if nodeAttacks then
				for k,v in pairs(nodeAttacks.getChildren()) do
					local sAttack = NodeManager.get(v, "value", "");
					local rPower = CombatCommon.parseCTAttackLine(sAttack);
					if rPower and rPower.usage_val and rPower.name == recharge_name then
						local sNew = string.sub(sAttack, 1, rPower.usage_startpos - 2) .. string.sub(sAttack, rPower.usage_endpos + 1);
						NodeManager.set(v, "value", "string", sNew);
					end
				end
			end
		end
	end

	-- DELIVER ROLL MESSAGE
	ChatManager.deliverMessage(entry);
end
